package org.apache.lucene.store;

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;

/** Base implementation class for buffered {@link IndexOutput}. */
public abstract class BufferedIndexOutput extends IndexOutput {
	static final int BUFFER_SIZE = 16384;

	private final byte[] buffer = new byte[BUFFER_SIZE];
	private long bufferStart = 0; // position in file of buffer
	private int bufferPosition = 0; // position in buffer

	/**
	 * Writes a single byte.
	 * 
	 * @see IndexInput#readByte()
	 */
	public void writeByte(byte b) throws IOException {
		if (bufferPosition >= BUFFER_SIZE)
			flush();
		buffer[bufferPosition++] = b;
	}

	/**
	 * Writes an array of bytes.
	 * 
	 * @param b
	 *            the bytes to write
	 * @param length
	 *            the number of bytes to write
	 * @see IndexInput#readBytes(byte[],int,int)
	 */
	public void writeBytes(byte[] b, int offset, int length) throws IOException {
		int bytesLeft = BUFFER_SIZE - bufferPosition;
		// is there enough space in the buffer?
		if (bytesLeft >= length) {
			// we add the data to the end of the buffer
			System.arraycopy(b, offset, buffer, bufferPosition, length);
			bufferPosition += length;
			// if the buffer is full, flush it
			if (BUFFER_SIZE - bufferPosition == 0)
				flush();
		} else {
			// is data larger then buffer?
			if (length > BUFFER_SIZE) {
				// we flush the buffer
				if (bufferPosition > 0)
					flush();
				// and write data at once
				flushBuffer(b, offset, length);
				bufferStart += length;
			} else {
				// we fill/flush the buffer (until the input is written)
				int pos = 0; // position in the input data
				int pieceLength;
				while (pos < length) {
					pieceLength = (length - pos < bytesLeft) ? length - pos
							: bytesLeft;
					System.arraycopy(b, pos + offset, buffer, bufferPosition,
							pieceLength);
					pos += pieceLength;
					bufferPosition += pieceLength;
					// if the buffer is full, flush it
					bytesLeft = BUFFER_SIZE - bufferPosition;
					if (bytesLeft == 0) {
						flush();
						bytesLeft = BUFFER_SIZE;
					}
				}
			}
		}
	}

	/** Forces any buffered output to be written. */
	public void flush() throws IOException {
		flushBuffer(buffer, bufferPosition);
		bufferStart += bufferPosition;
		bufferPosition = 0;
	}

	/**
	 * Expert: implements buffer write. Writes bytes at the current position in
	 * the output.
	 * 
	 * @param b
	 *            the bytes to write
	 * @param len
	 *            the number of bytes to write
	 */
	private void flushBuffer(byte[] b, int len) throws IOException {
		flushBuffer(b, 0, len);
	}

	/**
	 * Expert: implements buffer write. Writes bytes at the current position in
	 * the output.
	 * 
	 * @param b
	 *            the bytes to write
	 * @param offset
	 *            the offset in the byte array
	 * @param len
	 *            the number of bytes to write
	 */
	protected abstract void flushBuffer(byte[] b, int offset, int len)
			throws IOException;

	/** Closes this stream to further operations. */
	public void close() throws IOException {
		flush();
	}

	/**
	 * Returns the current position in this file, where the next write will
	 * occur.
	 * 
	 * @see #seek(long)
	 */
	public long getFilePointer() {
		return bufferStart + bufferPosition;
	}

	/**
	 * Sets current position in this file, where the next write will occur.
	 * 
	 * @see #getFilePointer()
	 */
	public void seek(long pos) throws IOException {
		flush();
		bufferStart = pos;
	}

	/** The number of bytes in the file. */
	public abstract long length() throws IOException;

}
